package com.chushouya.aop;

import com.alibaba.fastjson.JSON;
import com.chushouya.common.dto.OperateLogDTO;
import com.chushouya.common.support.Contexts;
import com.chushouya.common.support.RequestUserContext;
import com.chushouya.common.util.IpUtils;
import com.chushouya.manager.async.AsyncFactory;
import com.chushouya.manager.async.AsyncManager;
import com.general.framework.core.enums.BusinessStatus;
import com.general.framework.core.enums.HttpMethod;
import com.general.framework.core.lang.Dates;
import com.general.framework.core.lang.Jsons;
import com.general.framework.core.lang.Numbers;
import com.general.framework.core.lang.Strings;
import com.general.framework.web.annotations.Log;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * 上下文信息处理拦截器
 * @author leejean team
 */
@Aspect
@Component
@Order(202)
public class LogAspect {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(LogAspect.class);

    @Resource
    private HttpServletRequest request;

    @Value("${com.chushouya.manager.log.saveOperateLog:false}")
    private Boolean saveOperateLog;

    /**
     * 配置织入点
     */
    @Pointcut("@annotation(com.general.framework.web.annotations.Log)")
    public void logPointCut() {}

    /**
     * 处理完请求后执行
     *
     * @param joinPoint 切点
     */
    @AfterReturning(pointcut = "logPointCut()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        if (this.saveOperateLog) {
            this.handleLog(joinPoint, null, jsonResult);
        }
    }

    /**
     * 拦截异常操作
     *
     * @param joinPoint 切点
     * @param e         异常
     */
    @AfterThrowing(value = "logPointCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        if (this.saveOperateLog) {
            this.handleLog(joinPoint, e, null);
        }
    }

    private void handleLog(final JoinPoint joinPoint, final Exception e, Object jsonResult) {
        try {
            // 获得注解
            Log logAnnotation = getAnnotationLog(joinPoint);
            if (logAnnotation == null) {
                return;
            }
            // 获取当前的用户
            RequestUserContext userContext = Contexts.getUserContext();
            OperateLogDTO log = new OperateLogDTO();
            log.setStatus(BusinessStatus.SUCCESS.getCode());
            // 请求的地址
            String ip = IpUtils.getIpAddr(request);
            log.setIp(ip);
            // 返回参数
            if (Objects.nonNull(jsonResult) && logAnnotation.saveResponse()) {
                log.setResult(Jsons.toJsonString(jsonResult));
            }
            log.setUrl(request.getRequestURI());
            if (Strings.isNotEmpty(log.getUrl())) {
                log.setServiceType(log.getUrl().split("/")[2]);
            }
            log.setContextUser(userContext.getUserName());
            log.setContextUserType(userContext.getUserType());

            if (Objects.nonNull(e)) {
                log.setStatus(BusinessStatus.FAIL.getCode());
                log.setMsg(Strings.substring(e.getMessage(), 0, 2000));
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            log.setMethod(Strings.format("{}.{}()",className, methodName));
            // 设置请求方式
            log.setMethod(request.getMethod());
            // 处理设置注解上的参数
            getControllerMethodDescription(joinPoint, logAnnotation, log);
            log.setTime(Dates.getTimeNow());
            // 保存数据库
            AsyncManager.me().execute(AsyncFactory.saveOperateLog(log));
        } catch (Exception exp) {
            log.error("记录操作日志异常:{}", exp.getMessage() , exp);
        }
    }

    /**
     * 获取注解中对方法的描述信息
     */
    private void getControllerMethodDescription(JoinPoint joinPoint, Log logAnnotation, OperateLogDTO log){
        // 设置action动作
        log.setBusinessType(logAnnotation.businessType().getName());

        this.setMethodInfo(joinPoint, log);

        // 是否需要保存request，参数和值
        if (logAnnotation.saveRequest()) {
            // 获取参数的信息，传入到数据库中。
            setRequestValue(joinPoint, log);
        }
    }

    /**
     * 获取请求的参数，放到log中
     * @param dto 操作日志
     */
    private void setRequestValue(JoinPoint joinPoint, OperateLogDTO dto) {
        String requestMethod = dto.getMethod();
        if (HttpMethod.PUT.name().equals(requestMethod) || HttpMethod.POST.name().equals(requestMethod)|| HttpMethod.DELETE.name().equals(requestMethod)) {
            String params = argsArrayToString(joinPoint.getArgs());
            dto.setParam(Strings.substring(params, 0, 2000));
        } else {
            Map<?, ?> paramsMap = (Map<?, ?>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
            dto.setParam(Strings.substring(paramsMap.toString(), 0, 2000));
        }
    }

    /**
     * 是否存在注解，如果存在就获取
     */
    private Log getAnnotationLog(JoinPoint joinPoint) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (method != null) {
            return method.getAnnotation(Log.class);
        }
        return null;
    }
    private void setMethodInfo(JoinPoint joinPoint, OperateLogDTO log) {
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (Objects.nonNull(method)) {
            final Class<?> clazz = method.getDeclaringClass();
            if (Objects.nonNull(clazz)) {
                final Api api = clazz.getAnnotation(Api.class);
                if (Objects.nonNull(api)) {
                    if (api.tags().length > 0) {
                        log.setModelName(api.tags()[0]);
                    } else {
                        log.setModelName(api.value());
                    }
                }
            }

            final ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
            if (Objects.nonNull(apiOperation)) {
                log.setOperateName(apiOperation.value());
            }
        }


    }
    /**
     * 参数拼装
     */
    private String argsArrayToString(Object[] paramsArray) {
        StringBuilder params = new StringBuilder(Strings.EMPTY);
        if (Objects.nonNull(paramsArray) && paramsArray.length > Numbers.INTEGER_ZERO) {
            for (Object param : paramsArray) {
                if (Objects.nonNull(param) && !isFilterObject(param)) {
                    Object jsonObj = JSON.toJSON(param);
                    params.append(jsonObj.toString()).append(" ");
                }
            }
        }
        return params.toString().trim();
    }

    /**
     * 判断是否需要过滤的对象。
     * @param o 对象信息。
     * @return 如果是需要过滤的对象，则返回true；否则返回false。
     */
    private boolean isFilterObject(final Object o) {
        Class<?> clazz = o.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) o;
            for (Iterator iter = collection.iterator(); iter.hasNext(); ) {
                return iter.next() instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) o;
            for (Iterator iter = map.entrySet().iterator(); iter.hasNext(); ) {
                Map.Entry entry = (Map.Entry) iter.next();
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return o instanceof MultipartFile || o instanceof HttpServletRequest || o instanceof HttpServletResponse
                || o instanceof BindingResult;
    }

}
